"""
DingTalk Card Sender Web Application
"""
from fastapi import FastAPI, Request, HTTPException, Depends
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
import json
import os
import asyncio
from typing import Dict, List
from datetime import datetime
import secrets
import html
import re

# Import our DingTalk API client
from dingtalk_api import send_card_to_dingtalk
from auth import auth
from security import sanitize_input, validate_template_name, SendCardRequest, SaveTemplateRequest
from logging_config import log_api_call, log_security_event, log_error, log_info
from webhook_integration import webhook_receiver

# Initialize rate limiter
limiter = Limiter(key_func=get_remote_address)
app = FastAPI(title="DingTalk Card Sender")
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)


templates = Jinja2Templates(directory="templates")


def get_current_user(request: Request):
    """
    Get the current user from the session
    """
    session_id = request.cookies.get("session_id")
    if not session_id:
        return None
    
    username = auth.validate_session(session_id)
    if not username:
        return None
    
    return username


async def send_card_with_retry(card_content: Dict, webhook_url: str, secret: str = None) -> tuple:
    """
    Send a card with retry mechanism and return result and retry count
    """
    max_retries = 3
    retries = 0
    result = None
    
    while retries <= max_retries:
        result = send_card_to_dingtalk(card_content, webhook_url, secret)
        
        # If successful or no more retries, break
        if "error" not in result or retries >= max_retries:
            break
        
        # Increment retry count and wait before retrying
        retries += 1
        await asyncio.sleep(2 ** retries)  # Exponential backoff
    
    # Determine status based on result and retries
    status = "completed" if "error" not in result else "failed"
    
    return result, status, retries


# Directory to store card templates
TEMPLATES_DIR = "card_templates"
os.makedirs(TEMPLATES_DIR, exist_ok=True)

# Directory to store template metadata
METADATA_DIR = "template_metadata"
os.makedirs(METADATA_DIR, exist_ok=True)

# Directory to store template versions
VERSIONS_DIR = "template_versions"
os.makedirs(VERSIONS_DIR, exist_ok=True)

# Load configuration from settings.json
def load_settings():
    settings_file = "settings.json"
    if os.path.exists(settings_file):
        with open(settings_file, 'r', encoding='utf-8') as f:
            return json.load(f)
    else:
        # Return default empty values if settings file doesn't exist
        return {"dingtalk_webhook_url": "", "dingtalk_secret": ""}

settings = load_settings()
DINGTALK_WEBHOOK_URL = settings.get("dingtalk_webhook_url", "")
DINGTALK_SECRET = settings.get("dingtalk_secret", "")


@app.post("/api/register")
async def register_user(request: Request):
    """
    Register a new user
    """
    data = await request.json()
    username = data.get("username")
    password = data.get("password")
    email = data.get("email", "")
    
    if not username or not password:
        return JSONResponse(status_code=400, content={"message": "Username and password are required"})
    
    if len(password) < 6:
        return JSONResponse(status_code=400, content={"message": "Password must be at least 6 characters"})
    
    if auth.create_user(username, password, email):
        return {"message": "User registered successfully"}
    else:
        return JSONResponse(status_code=409, content={"message": "User already exists"})


@app.post("/api/login")
async def login_user(request: Request):
    """
    Login a user and create a session
    """
    data = await request.json()
    username = data.get("username")
    password = data.get("password")
    
    if not username or not password:
        return JSONResponse(status_code=400, content={"message": "Username and password are required"})
    
    user = auth.authenticate_user(username, password)
    if user:
        session_id = auth.create_session(username)
        response = JSONResponse(content={"message": "Login successful", "username": username})
        response.set_cookie(
            key="session_id",
            value=session_id,
            httponly=True,
            max_age=86400,  # 24 hours
            expires=86400,
            samesite="lax"
        )
        return response
    else:
        return JSONResponse(status_code=401, content={"message": "Invalid credentials"})


@app.post("/api/logout")
async def logout_user(request: Request):
    """
    Logout a user and delete their session
    """
    session_id = request.cookies.get("session_id")
    if session_id:
        auth.delete_session(session_id)
    
    response = JSONResponse(content={"message": "Logged out successfully"})
    response.delete_cookie("session_id")
    return response

def get_template_metadata(template_name: str) -> Dict:
    """Get metadata for a template, including category"""
    metadata_path = os.path.join(METADATA_DIR, f"{template_name}.json")
    if os.path.exists(metadata_path):
        with open(metadata_path, 'r', encoding='utf-8') as f:
            return json.load(f)
    # Return default metadata if not found
    return {
        "name": template_name,
        "category": "默认分类",
        "tags": [],
        "created_at": datetime.now().isoformat(),
        "updated_at": datetime.now().isoformat()
    }

def save_template_metadata(template_name: str, metadata: Dict):
    """Save metadata for a template"""
    metadata_path = os.path.join(METADATA_DIR, f"{template_name}.json")
    metadata["updated_at"] = datetime.now().isoformat()
    with open(metadata_path, 'w', encoding='utf-8') as f:
        json.dump(metadata, f, ensure_ascii=False, indent=2)


def save_template_version(template_name: str, content: Dict):
    """Save a new version of a template"""
    # Create version directory for the template if it doesn't exist
    template_version_dir = os.path.join(VERSIONS_DIR, template_name)
    os.makedirs(template_version_dir, exist_ok=True)
    
    # Get the next version number
    existing_versions = []
    for filename in os.listdir(template_version_dir):
        if filename.endswith(".json"):
            version_num = int(filename[:-5])  # Remove .json and convert to int
            existing_versions.append(version_num)
    
    next_version = max(existing_versions) + 1 if existing_versions else 1
    
    # Save the new version
    version_path = os.path.join(template_version_dir, f"{next_version}.json")
    with open(version_path, 'w', encoding='utf-8') as f:
        json.dump(content, f, ensure_ascii=False, indent=2)
    
    return next_version


def get_template_versions(template_name: str) -> List[Dict]:
    """Get all versions of a template"""
    template_version_dir = os.path.join(VERSIONS_DIR, template_name)
    if not os.path.exists(template_version_dir):
        return []
    
    versions = []
    for filename in os.listdir(template_version_dir):
        if filename.endswith(".json"):
            version_num = int(filename[:-5])
            version_path = os.path.join(template_version_dir, filename)
            with open(version_path, 'r', encoding='utf-8') as f:
                content = json.load(f)
            versions.append({
                "version": version_num,
                "content": content,
                "saved_at": datetime.fromtimestamp(os.path.getmtime(version_path)).isoformat()
            })
    
    # Sort by version number in descending order (newest first)
    versions.sort(key=lambda x: x["version"], reverse=True)
    return versions


def get_template_version_content(template_name: str, version: int) -> Dict:
    """Get specific version of a template"""
    version_path = os.path.join(VERSIONS_DIR, template_name, f"{version}.json")
    if not os.path.exists(version_path):
        return None
    
    with open(version_path, 'r', encoding='utf-8') as f:
        return json.load(f)

@app.get("/", response_class=HTMLResponse)
async def read_root(request: Request):
    """
    Main page of the application
    """
    return templates.TemplateResponse("index.html", {"request": request})

@app.get("/api/templates")
async def get_templates(category: str = None, current_user: str = Depends(get_current_user)):
    """
    Get list of saved card templates for the current user
    """
    if not current_user:
        raise HTTPException(status_code=401, detail="Not authenticated")
    
    templates_list = []
    for filename in os.listdir(TEMPLATES_DIR):
        if filename.endswith(".json"):
            template_name = filename[:-5]  # Remove .json extension
            filepath = os.path.join(TEMPLATES_DIR, filename)
            
            # Check if template belongs to current user
            if not auth.is_template_owned_by_user(current_user, template_name):
                continue
            
            # Get template metadata
            metadata = get_template_metadata(template_name)
            
            # Filter by category if specified
            if category and metadata.get("category") != category:
                continue
                
            with open(filepath, 'r', encoding='utf-8') as f:
                template = json.load(f)
                templates_list.append({
                    "name": template_name,
                    "content": template,
                    "category": metadata.get("category", "默认分类"),
                    "tags": metadata.get("tags", []),
                    "created_at": metadata.get("created_at"),
                    "updated_at": metadata.get("updated_at")
                })
    return {"templates": templates_list}


@app.get("/api/categories")
async def get_categories():
    """
    Get list of all template categories
    """
    categories = set()
    for filename in os.listdir(TEMPLATES_DIR):
        if filename.endswith(".json"):
            template_name = filename[:-5]  # Remove .json extension
            metadata = get_template_metadata(template_name)
            categories.add(metadata.get("category", "默认分类"))
    
    # Add default categories if they don't exist
    if not categories:
        categories.update(["通知类", "告警类", "审批类", "默认分类"])
    
    return {"categories": sorted(list(categories))}


@app.get("/api/search")
async def search_templates(q: str = None, category: str = None, tag: str = None, current_user: str = Depends(get_current_user)):
    """
    Search templates by query, category, or tag for the current user
    """
    if not current_user:
        raise HTTPException(status_code=401, detail="Not authenticated")
    
    templates_list = []
    for filename in os.listdir(TEMPLATES_DIR):
        if filename.endswith(".json"):
            template_name = filename[:-5]  # Remove .json extension
            filepath = os.path.join(TEMPLATES_DIR, filename)
            
            # Check if template belongs to current user
            if not auth.is_template_owned_by_user(current_user, template_name):
                continue
            
            # Get template metadata
            metadata = get_template_metadata(template_name)
            
            # Apply filters
            if category and metadata.get("category") != category:
                continue
                
            # Check if template matches tag filter
            if tag:
                tags = metadata.get("tags", [])
                if tag not in tags:
                    continue
            
            with open(filepath, 'r', encoding='utf-8') as f:
                template = json.load(f)
                
                # Check if template matches text query
                matches_query = True
                if q:
                    # Search in template content
                    template_str = json.dumps(template, ensure_ascii=False)
                    matches_query = q.lower() in template_str.lower() or q in template_name
                    
                    # Also search in metadata fields
                    if not matches_query:
                        matches_query = (q in metadata.get("category", "") or 
                                       q in " ".join(metadata.get("tags", [])))
                
                if matches_query:
                    templates_list.append({
                        "name": template_name,
                        "content": template,
                        "category": metadata.get("category", "默认分类"),
                        "tags": metadata.get("tags", []),
                        "created_at": metadata.get("created_at"),
                        "updated_at": metadata.get("updated_at")
                    })
    
    return {"templates": templates_list}

@app.post("/api/templates/{name}")
@limiter.limit("20/hour")  # Limit to 20 template saves per hour per IP
async def save_template(name: str, request: Request, current_user: str = Depends(get_current_user)):
    """
    Save a card template for the current user
    """
    if not current_user:
        raise HTTPException(status_code=401, detail="Not authenticated")
    
    data = await request.json()
    template_data = data.get("content", data)  # Handle both raw content and structured data
    category = data.get("category", "默认分类")  # Get category from request
    tags = data.get("tags", [])  # Get tags from request
    
    filepath = os.path.join(TEMPLATES_DIR, f"{name}.json")
    with open(filepath, 'w', encoding='utf-8') as f:
        json.dump(template_data, f, ensure_ascii=False, indent=2)
    
    # Save a new version
    version_number = save_template_version(name, template_data)
    
    # Save metadata with category and tags
    metadata = get_template_metadata(name)
    metadata["category"] = category
    metadata["tags"] = tags
    metadata["latest_version"] = version_number
    save_template_metadata(name, metadata)
    
    # Add template to user's template list
    auth.add_user_template(current_user, name)
    
    return {
        "message": f"Template {name} saved successfully", 
        "category": category, 
        "tags": tags,
        "version": version_number
    }


@app.get("/api/templates/{name}/versions")
async def get_template_versions_endpoint(name: str, current_user: str = Depends(get_current_user)):
    """
    Get all versions of a template for the current user
    """
    if not current_user:
        raise HTTPException(status_code=401, detail="Not authenticated")
    
    # Check if template belongs to current user
    if not auth.is_template_owned_by_user(current_user, name):
        raise HTTPException(status_code=403, detail="Not authorized to access this template")
    
    versions = get_template_versions(name)
    return {"versions": versions, "template_name": name}


@app.get("/api/templates/{name}/versions/{version}")
async def get_template_version(name: str, version: int, current_user: str = Depends(get_current_user)):
    """
    Get specific version of a template for the current user
    """
    if not current_user:
        raise HTTPException(status_code=401, detail="Not authenticated")
    
    # Check if template belongs to current user
    if not auth.is_template_owned_by_user(current_user, name):
        raise HTTPException(status_code=403, detail="Not authorized to access this template")
    
    content = get_template_version_content(name, version)
    if content is None:
        return JSONResponse(status_code=404, content={"message": f"Version {version} of template {name} not found"})
    
    return {"version": version, "content": content, "template_name": name}


@app.post("/api/templates/{name}/versions/{version}/restore")
async def restore_template_version(name: str, version: int, current_user: str = Depends(get_current_user)):
    """
    Restore a specific version of a template for the current user
    """
    if not current_user:
        raise HTTPException(status_code=401, detail="Not authenticated")
    
    # Check if template belongs to current user
    if not auth.is_template_owned_by_user(current_user, name):
        raise HTTPException(status_code=403, detail="Not authorized to restore this template")
    
    content = get_template_version_content(name, version)
    if content is None:
        return JSONResponse(status_code=404, content={"message": f"Version {version} of template {name} not found"})
    
    # Update the current template file with the version content
    filepath = os.path.join(TEMPLATES_DIR, f"{name}.json")
    with open(filepath, 'w', encoding='utf-8') as f:
        json.dump(content, f, ensure_ascii=False, indent=2)
    
    # Update metadata
    metadata = get_template_metadata(name)
    metadata["latest_version"] = version
    save_template_metadata(name, metadata)
    
    return {"message": f"Template {name} restored to version {version}", "version": version}


@app.get("/api/templates/{name}/export")
async def export_template(name: str, current_user: str = Depends(get_current_user)):
    """
    Export a template as JSON file for the current user
    """
    if not current_user:
        raise HTTPException(status_code=401, detail="Not authenticated")
    
    # Check if template belongs to current user
    if not auth.is_template_owned_by_user(current_user, name):
        raise HTTPException(status_code=403, detail="Not authorized to export this template")
    
    # Get the current template content
    filepath = os.path.join(TEMPLATES_DIR, f"{name}.json")
    if not os.path.exists(filepath):
        return JSONResponse(status_code=404, content={"message": f"Template {name} not found"})
    
    with open(filepath, 'r', encoding='utf-8') as f:
        content = json.load(f)
    
    # Get template metadata
    metadata = get_template_metadata(name)
    
    # Combine content and metadata for export
    export_data = {
        "name": name,
        "content": content,
        "metadata": metadata,
        "exported_at": datetime.now().isoformat()
    }
    
    return export_data


@app.post("/api/templates/import")
@limiter.limit("10/hour")  # Limit to 10 template imports per hour per IP
async def import_template(request: Request, current_user: str = Depends(get_current_user)):
    """
    Import a template from JSON data for the current user
    """
    if not current_user:
        raise HTTPException(status_code=401, detail="Not authenticated")
    
    try:
        data = await request.json()
        
        if "name" not in data or "content" not in data:
            return JSONResponse(
                status_code=400, 
                content={"message": "Invalid template data. Must include 'name' and 'content'."}
            )
        
        name = data["name"]
        content = data["content"]
        metadata = data.get("metadata", {})
        
        # Save the template content
        filepath = os.path.join(TEMPLATES_DIR, f"{name}.json")
        with open(filepath, 'w', encoding='utf-8') as f:
            json.dump(content, f, ensure_ascii=False, indent=2)
        
        # Save template version
        save_template_version(name, content)
        
        # Save metadata
        template_metadata = get_template_metadata(name)
        template_metadata.update({
            "category": metadata.get("category", template_metadata.get("category", "默认分类")),
            "tags": metadata.get("tags", template_metadata.get("tags", [])),
            "latest_version": metadata.get("latest_version", 1)
        })
        save_template_metadata(name, template_metadata)
        
        # Add template to user's template list
        auth.add_user_template(current_user, name)
        
        return {"message": f"Template {name} imported successfully", "name": name}
        
    except json.JSONDecodeError:
        return JSONResponse(status_code=400, content={"message": "Invalid JSON format"})
    except Exception as e:
        return JSONResponse(status_code=500, content={"message": f"Import failed: {str(e)}"})

@app.delete("/api/templates/{name}")
async def delete_template(name: str, current_user: str = Depends(get_current_user)):
    """
    Delete a card template for the current user
    """
    if not current_user:
        raise HTTPException(status_code=401, detail="Not authenticated")
    
    # Check if template belongs to current user
    if not auth.is_template_owned_by_user(current_user, name):
        raise HTTPException(status_code=403, detail="Not authorized to delete this template")
    
    filepath = os.path.join(TEMPLATES_DIR, f"{name}.json")
    if os.path.exists(filepath):
        os.remove(filepath)
        
        # Remove template from user's template list
        auth.remove_user_template(current_user, name)
        
        # Also remove associated metadata and versions
        metadata_path = os.path.join(METADATA_DIR, f"{name}.json")
        if os.path.exists(metadata_path):
            os.remove(metadata_path)
            
        version_dir = os.path.join(VERSIONS_DIR, name)
        if os.path.exists(version_dir):
            import shutil
            shutil.rmtree(version_dir)
        
        return {"message": f"Template {name} deleted successfully"}
    return {"error": "Template not found"}

@app.post("/api/send")
async def send_card(request: Request):
    """
    Send a card to DingTalk
    """
    card_data = await request.json()
    
    # In a real application, you'd want to validate webhook URL and secret
    # For now, using values from environment/config
    if not DINGTALK_WEBHOOK_URL:
        return {"error": "DingTalk webhook URL not configured"}, 400
        
    result = send_card_to_dingtalk(card_data, DINGTALK_WEBHOOK_URL, DINGTALK_SECRET)
    
    if "error" in result:
        return {"error": result["error"]}, 500
    
    return {"message": "Card sent successfully", "result": result}


# Schedule storage (in a real app, you'd use a database)
SCHEDULES_DIR = "scheduled_tasks"
os.makedirs(SCHEDULES_DIR, exist_ok=True)

# History storage
HISTORY_DIR = "send_history"
os.makedirs(HISTORY_DIR, exist_ok=True)

# Simple task scheduler using a background thread
import threading
import time
from datetime import datetime

scheduled_tasks = {}

def run_scheduled_tasks():
    """
    Background function to run scheduled tasks
    """
    while True:
        current_time = datetime.now()
        tasks_to_run = []
        
        # Find tasks that should run now
        for task_id, task_info in scheduled_tasks.items():
            scheduled_time = datetime.fromisoformat(task_info['scheduled_time'])
            if current_time >= scheduled_time and task_info.get('status') == 'scheduled':
                tasks_to_run.append((task_id, task_info))
        
        # Execute the tasks
        for task_id, task_info in tasks_to_run:
            try:
                # Update status in memory
                scheduled_tasks[task_id]['status'] = 'executing'
                
                # Send the card with retry mechanism (for sync context)
                webhook_url = task_info.get("webhook_url") or DINGTALK_WEBHOOK_URL
                secret = task_info.get("secret") or DINGTALK_SECRET
                content = task_info.get("content")
                
                # For the scheduled tasks thread, we'll implement the retry logic here
                max_retries = 3
                retries = 0
                result = None
                
                while retries <= max_retries:
                    result = send_card_to_dingtalk(content, webhook_url, secret)
                    
                    # If successful or no more retries, break
                    if "error" not in result or retries >= max_retries:
                        break
                    
                    # Increment retry count and wait before retrying
                    retries += 1
                    time.sleep(2 ** retries)  # Exponential backoff
                
                # Determine status based on result and retries
                status = "completed" if "error" not in result else "failed"
                
                # Update task status
                scheduled_tasks[task_id]['status'] = status
                scheduled_tasks[task_id]['result'] = result
                scheduled_tasks[task_id]['completed_at'] = datetime.now().isoformat()
                scheduled_tasks[task_id]['retries'] = retries
                
                print(f"Scheduled task {task_id} executed: {result}")
                
            except Exception as e:
                scheduled_tasks[task_id]['status'] = 'failed'
                scheduled_tasks[task_id]['error'] = str(e)
                print(f"Scheduled task {task_id} failed: {str(e)}")
        
        time.sleep(30)  # Check every 30 seconds

# Start the scheduler in a background thread
scheduler_thread = threading.Thread(target=run_scheduled_tasks, daemon=True)
scheduler_thread.start()

@app.post("/api/batch-send")
@limiter.limit("2/minute")  # Limit to 2 batch sends per minute per IP
async def batch_send_cards(request: Request):
    """
    Send multiple cards to DingTalk in batch
    Expected format: {"cards": [{"webhook_url": "...", "secret": "...", "content": {...}}, ...]}
    """
    data = await request.json()
    cards = data.get("cards", [])
    
    if not isinstance(cards, list):
        return {"error": "Expected 'cards' to be a list"}, 400

    results = []
    for i, card in enumerate(cards):
        try:
            webhook_url = card.get("webhook_url") or DINGTALK_WEBHOOK_URL
            secret = card.get("secret") or DINGTALK_SECRET
            content = card.get("content")
            
            if not webhook_url or not content:
                results.append({
                    "index": i,
                    "status": "error",
                    "message": "Missing webhook_url or content"
                })
                continue

            # Send card with retry mechanism
            result, status, retries = await send_card_with_retry(content, webhook_url, secret)
            
            results.append({
                "index": i,
                "status": "success" if "error" not in result else "error",
                "result": result,
                "retries": retries
            })
            
        except Exception as e:
            results.append({
                "index": i,
                "status": "error",
                "message": str(e)
            })
    
    successful_count = len([r for r in results if r["status"] == "success"])
    failed_count = len([r for r in results if r["status"] == "error"])
    
    return {
        "message": f"Batch send completed. Success: {successful_count}, Failed: {failed_count}",
        "results": results
    }

@app.post("/api/schedule-send")
@limiter.limit("10/minute")  # Limit to 10 schedule requests per minute per IP
async def schedule_send_card(request: Request):
    """
    Schedule a card to be sent at a specific time
    Expected format: {"scheduled_time": "YYYY-MM-DDTHH:MM:SS", "webhook_url": "...", "secret": "...", "content": {...}}
    """
    data = await request.json()
    
    required_fields = ["scheduled_time", "content"]
    for field in required_fields:
        if field not in data:
            return {"error": f"Missing required field: {field}"}, 400
    
    scheduled_time = data["scheduled_time"]
    webhook_url = data.get("webhook_url") or DINGTALK_WEBHOOK_URL
    secret = data.get("secret") or DINGTALK_SECRET
    content = data["content"]
    
    # Validate scheduled time format
    try:
        datetime.fromisoformat(scheduled_time.replace("Z", "+00:00"))
    except ValueError:
        return {"error": "Invalid scheduled_time format, use ISO format: YYYY-MM-DDTHH:MM:SS"}, 400
    
    # Generate a unique task ID
    import uuid
    task_id = str(uuid.uuid4())
    
    # Store the scheduled task
    task_info = {
        "id": task_id,
        "scheduled_time": scheduled_time,
        "webhook_url": webhook_url,
        "secret": secret,
        "content": content,
        "status": "scheduled",
        "created_at": datetime.now().isoformat()
    }
    
    scheduled_tasks[task_id] = task_info
    
    # Also save to file for persistence
    task_file = os.path.join(SCHEDULES_DIR, f"{task_id}.json")
    with open(task_file, 'w', encoding='utf-8') as f:
        json.dump(task_info, f, ensure_ascii=False, indent=2)
    
    return {
        "message": "Card scheduled successfully",
        "task_id": task_id,
        "scheduled_time": scheduled_time
    }

@app.get("/api/scheduled-tasks")
async def get_scheduled_tasks():
    """
    Get list of all scheduled tasks
    """
    return {"tasks": list(scheduled_tasks.values())}

@app.delete("/api/scheduled-tasks/{task_id}")
async def cancel_scheduled_task(task_id: str):
    """
    Cancel a scheduled task
    """
    if task_id not in scheduled_tasks:
        return {"error": "Task not found"}, 404
    
    # Update status to cancelled
    scheduled_tasks[task_id]['status'] = 'cancelled'
    
    # Update the file
    task_file = os.path.join(SCHEDULES_DIR, f"{task_id}.json")
    with open(task_file, 'w', encoding='utf-8') as f:
        json.dump(scheduled_tasks[task_id], f, ensure_ascii=False, indent=2)
    
    return {"message": f"Task {task_id} cancelled successfully"}


def save_send_history(card_data: Dict, result: Dict, webhook_url: str = None, status: str = "completed", retries: int = 0):
    """
    Save send history to file
    """
    import uuid
    history_id = str(uuid.uuid4())
    
    history_entry = {
        "id": history_id,
        "timestamp": datetime.now().isoformat(),
        "webhook_url": webhook_url or DINGTALK_WEBHOOK_URL,
        "content": card_data,
        "result": result,
        "msgtype": card_data.get("msgtype", "unknown"),
        "status": status,  # completed, failed, retrying
        "retries": retries
    }
    
    history_file = os.path.join(HISTORY_DIR, f"{history_id}.json")
    with open(history_file, 'w', encoding='utf-8') as f:
        json.dump(history_entry, f, ensure_ascii=False, indent=2)
    
    return history_entry


@app.get("/api/send-history")
async def get_send_history():
    """
    Get send history
    """
    history_list = []
    
    for filename in os.listdir(HISTORY_DIR):
        if filename.endswith(".json"):
            filepath = os.path.join(HISTORY_DIR, filename)
            with open(filepath, 'r', encoding='utf-8') as f:
                history_entry = json.load(f)
                history_list.append(history_entry)
    
    # Sort by timestamp, newest first
    history_list.sort(key=lambda x: x["timestamp"], reverse=True)
    
    return {"history": history_list}


@app.delete("/api/send-history/{history_id}")
async def delete_send_history(history_id: str):
    """
    Delete a specific history entry
    """
    history_file = os.path.join(HISTORY_DIR, f"{history_id}.json")
    if os.path.exists(history_file):
        os.remove(history_file)
        return {"message": f"History entry {history_id} deleted successfully"}
    else:
        return {"error": "History entry not found"}, 404


# Override the original send_card endpoint to also save history
@app.post("/api/send")
@limiter.limit("5/minute")  # Limit to 5 sends per minute per IP
async def send_card(request: Request):
    """
    Send a card to DingTalk and save history with retry mechanism
    """
    card_data = await request.json()
    
    # In a real application, you'd want to validate webhook URL and secret
    # For now, using values from environment/config
    if not DINGTALK_WEBHOOK_URL:
        # Check if webhook is provided in the request
        webhook_url = card_data.get('webhook_url')
        if not webhook_url:
            return {"error": "DingTalk webhook URL not configured"}, 400
    else:
        webhook_url = DINGTALK_WEBHOOK_URL
    
    # Extract webhook_url and secret from card_data if provided
    if isinstance(card_data, dict) and 'webhook_url' in card_data:
        webhook_url = card_data['webhook_url']
        secret = card_data.get('secret') or DINGTALK_SECRET
        card_content = card_data.get('content', card_data)
    else:
        secret = DINGTALK_SECRET
        card_content = card_data
    
    # Send card with retry mechanism
    result, status, retries = await send_card_with_retry(card_content, webhook_url, secret)
    
    # Save to history with status and retry info
    history_entry = save_send_history(card_content, result, webhook_url, status, retries)
    
    if "error" in result:
        return {
            "error": result["error"], 
            "history_id": history_entry["id"],
            "retries": retries,
            "message": f"Card sending failed after {retries} retries" if retries > 0 else "Card sending failed"
        }, 500
    
    return {
        "message": f"Card sent successfully after {retries} retries" if retries > 0 else "Card sent successfully", 
        "result": result, 
        "history_id": history_entry["id"],
        "retries": retries
    }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)